﻿using System.Collections.Generic;
using System;
using System.Diagnostics;
using System.Linq;
using Buddy.BehaviorTree;
using Buddy.CommonBot;
using Buddy.Common.Math;
using Buddy.Swtor;
using Buddy.Swtor.Objects;

using WingIt.Routines;
using WingIt.Routines.Mirrors;

using Action = Buddy.BehaviorTree.Action;


namespace WingIt
{
    /// <summary>
    /// <para>TargetSelect makes choices of the best target for the stated purpose based on current battlefield conditions.
    /// The entire TargetSelect system is based on the "separation of concerns" principle:<list type="bullet">
    /// <item><description><para> * The TargetSelect method select "onWhom" and "when".</para></description></item>
    /// <item><description><para> * The CC selects "how much" and whether or not a particular ability should be made
    /// available to service the "onWhom" and "when".</para></description></item>
    /// <para>The TargetSelect system alters its choices based on the current battlefield situation, and the member's
    /// role in a party (or if it is solo).  All of the For*() methods here return "null" to indicate "you've nothing to do
    /// at this time".</para>
    /// </list></para>
    /// </summary>
    /// 17Jul2012-06:46UTC chinajade
    [RevisionControlId("$Id: TargetSelect.cs 692 2012-08-13 13:34:27Z Chinajade $")]
    public static class TargetSelect
    {
        public delegate bool TorCharacterPredicateDelegate(TorCharacter torCharacter);
        public static TorCharacter NoHostileTarget { get { return BuddyTor.Me; } }

        #region Access Convenience
        private static TorPlayer Me { get { return (BuddyTor.Me); } }
        #endregion

        public static bool IsAtThePull()
        {
            return !Me.InCombat && (Me.CurrentTarget != null) && (Poi.Current.Type == PoiType.Kill);
        }

        /// <summary>
        /// <para>Vacates any cached TargetSelect information and forces a re-evaluation as the information is needed.</para>
        /// </summary>
        /// <returns></returns>
        public static Composite CreateBTPS_UpdateForBattlefieldConditions()
        {
            return (new PrioritySelector(
                DebugTools.CreateBTPS_DumpBehaviorTreePulseRate(measurePulseRateWhen => Global.Debug.MeasureBehaviorTreePulseRate),

                new Action(vacateCache => {
                    Cache.Vacate();
                    return (RunStatus.Failure); // fall through
                    }),

                new Decorator(whenInCombat => Me.InCombat || TargetSelect.IsAtThePull(),
                    new Action(inCombat => {
                        TorCharacter target = ForAttack();

                        // If target is invalid, print warning and prevent further evaluation of parent PrioritySelector...
                        // This happens due to bugs in BWcore occasionally returning 'null' for BuddyTor.Me.CurrentTarget.  If we allow the condition
                        // to propagate to our parent PrioritySelector, exceptions will ensue as the PS's children try to evaluate 'distance',
                        // 'health', etc on a non-existent target.
                        if ((target == null) || target.IsDeleted)
                        {
                            Logger.MaintenanceError("No attack target set--{0}",
                                ((target == null) ? "target is null"
                                 : (target.IsDeleted) ? "target deleted"
                                 : "target error unknown"));
                            return RunStatus.Success;
                        }

                        // If "no hostile target", prevent further evaluation of parent PrioritySelector...
                        // This can legitimately happen for several reasons, but mostly due to unbroken, crowd-controlled mobs.  For instance,
                        // we're in a party and we're not the tank.  While still in combat, the tank has yet to break any of the remaining
                        // CC'd mobs.  In such cases, we want to 'idle' until the tank decides when & which mob to break.
                        if (target == TargetSelect.NoHostileTarget)
                            { return RunStatus.Success; }

                        // We've an addressable target, fall through to allow parent PS children to evaluate...
                        return (RunStatus.Failure);     
                        }))
                ));
        }

        /// <summary>
        /// <para>Returns the target we should be attacking.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If we're not in combat, all mobs we anticipate to come if the CurrentTarget is pulled will be evaluated.</para></description></item>
        /// <item><description><para> * This value may not be the same as CurrentTarget.</para></description></item>
        /// <item><description><para> * The selected target may change based on our group role.
        /// For instance, a group's tank may have a different target choice, than a soloing caster.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-06:46UTC chinajade
        public static TorCharacter ForAttack()
        {
            // If we're not managing the battlefield, honor BW's choice of current target...
            // We don't want to manage the battlefield in certain situations, such as when the user is controlling
            // movement.  The current target is the only one a user can 'see' (and thus position for attacking).
            if (!LazyRaider.IsBattlefieldManaged)
                { return BuddyTor.Me.CurrentTarget; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForAttack != null)
                { return Cache.TargetForAttack; }

            // Taunt target is a priority--if taunt target is available, knock it down...
            // NB: ForTaunt() arranges for the tank to taunt any significant mob (not trash) not attacking the Tank.
            // ForTaunt() only asks non-Tanks to 'taunt' (attack) mobs attacking the healer.
            if (ForTaunt() != null)
                return Cache.TargetForTaunt;

            // Tanks in a group should go after the most difficult target...
            // Solo play or non-tanks knock down the easiest targets.
            // NB: toons can 'switch targets' based on this code.  Tanks will always go after the 'difficult target'
            // should that change during battle, and others will always go after the easiest targets, if that changes.
            bool isInParty = Me.IsInParty();
            bool isMeTank  = (Tank == Me);
            Cache.TargetForAttack = (isInParty && isMeTank)
                                    ? GetMostDifficultTarget(m => !m.IsCrowdControlled())
                                    : GetEasiestTarget(m => !m.IsCrowdControlled() && m.InLineOfSight);
            if (Cache.TargetForAttack != null)
                { return Cache.TargetForAttack; }

            // If we're in a group with a player tank, wait for him to break a CC target, and assist...
            bool isTankAPlayer = ((Tank as TorPlayer) != null);
            if (isInParty && isTankAPlayer && !isMeTank)
                { return NoHostileTarget; } // should never return 'null' while in combat

            // Otherwise, we're not in group, or we have an NPC tank... knock down the easiest target
            // TODO: (future enhancement) perhaps we should go after the mob with the shortest CC duration remaining.
            Cache.TargetForAttack = GetEasiestTarget();
            if (Cache.TargetForAttack != null)
                { return Cache.TargetForAttack; }

            // Last resort, honor BW's choice...
            Cache.TargetForAttack = Me.CurrentTarget;
            if (Cache.TargetForAttack != null)
                { return Cache.TargetForAttack; }

            // If not in combat, return NoHostileTarget so healing specs can move forward with OOC healing...
            return (!Me.InCombat ? NoHostileTarget : null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-23:09UTC chinajade
        public static TorCharacter ForCompanionAttack()
        {
            // TODO: figure this out (Currently, gated by lack of BW API for Companion management)
            return (ForAttack());
        }

        /// <summary>
        /// <para>Returns the best mob choice for crowd control.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * An appropriate CC choice will be returned if not in combat, but we have a target selected.
        /// This allows the Routine to pull by using crowd control.  The target selected for crowd control may not be the same as the CurrentTarget.</para></description></item>
        /// <item><description><para> * There must be at least 3 mobs attacking the party before crowd control will be considered.</para></description></item>
        /// <item><description><para> * "Lesser" mobs (not a substantive threat to the player) will not be considered for crowd control.</para></description></item>
        /// <item><description><para> * Mobs that are already crowd controlled will not be considered in target evaluation.</para></description></item>
        /// <item><description><para> * This method may return null.</para></description></item>
        /// <item><description><para> * An "example use" is included as comments above the method definition.</para></description></item>
        /// <item><description><para> * BUG: At this time, the Scoundrel's "Tranquilizer" and the Agent's "Sleep Dart" ability leave no
        /// debuff on a target.  Thus, we cannot 'see' such targets as CC'd, and they may be selected for further CC.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// Neo93
        ///
        // // Example use:
        // // ForCrowdControl(with arguments) forces evaluation of a new target, and caches it...
        // new Decorator(ctx => ForCrowdControl(Global.Distance.Melee) != null,
        //      new PrioritySelector(
        //          // ForCrowdControl("no arguments") uses the cached value
        //         Cast("MeleeCrowdControlSpell1", meleeCrowdControl1On => TargetSelect.ForCrowdControl()),
        //         Cast("MeleeCrowdControlSpell2", meleeCrowdControl2On => TargetSelect.ForCrowdControl())
        //         ))
        //
        // // ForCrowdControl(with arguments) forces evaluation of a new target, and caches it...
        // new Decorator(ctx => ForCrowdControl(Global.Distance.Ranged) != null,
        //      new PrioritySelector(
        //          // ForCrowdControl("no arguments") uses the cached value
        //         Cast("RangeCrowdControlSpell3", rangeCrowdControl3On => TargetSelect.ForCrowdControl()),
        //         Cast("RangeCrowdControlSpell4", rangeCrowdControl4On => TargetSelect.ForCrowdControl())
        //         ))
        public static TorCharacter ForCrowdControl(TorCharacterPredicateDelegate characterQualifier = null)
        {
            // If qualifier provided, mark the cache as 'stale'...
            // We must assume the qualifier has changed since the last call.
            if (characterQualifier != null)
                { Cache.TargetForCrowdControl = null; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForCrowdControl != null)
                { return (Cache.TargetForCrowdControl); }

            characterQualifier = characterQualifier ?? (c => true);
            var viableTargetList = ViableTargetList();
            
            // If we don't have at least 3 mobs engaged, no need for crowd control...
            if (viableTargetList.Count() < 3)
            {
                Cache.TargetForCrowdControl = null;
                return Cache.TargetForCrowdControl;
            }

            // NB: As our health declines, _our_ RelativeDifficulty also declines.  As such, mobs that normally
            // wouldn't be a threat may become so.  Me.RelativeDifficulty() scales the target selections for
            // CC based on our health.
            float myRelativeDifficulty = Me.RelativeDifficulty();

            // TODO: for stealth-users, may need to alter this to prefer an 'outlier' target...
            // NB: calculations are bsed on RelativeDifficulty instead of TargetValue() because the distance
            // of the target has no bearing on the importance of taking the target out of the fight.
            Cache.TargetForCrowdControl = viableTargetList
                                            .OrderByDescending(t => t.RelativeDifficulty())
                                            .FirstOrDefault(c => (c != TargetSelect.ForAttack())
                                                                  && characterQualifier(c) && !c.IsCrowdControlled()
                                                                  && (c.RelativeDifficulty() > myRelativeDifficulty));
            return (Cache.TargetForCrowdControl);
        }

        /// <summary>
        /// <para>Returns the party member most in need of a dispell.  If CONSIDERCOMPANIONS is true, companions are also
        /// consider in the dispell evaluation.</para>
        /// <para>Party members (including companions) may be further qualified with CHARACTERQUALIFIER.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * Calling this method with 'no arguments' will prefer to use a previously calculated cached value, if one is available.</para>
        /// <para>If no cached value is available, an appropriate choice will be made, cached, and returned.</para></description></item>
        /// <item><description><para> * Calling this method with any argument will force the cache to be marked as 'stale',
        /// and re-evaluation occurs in accordance with the provided arguments.</para></description></item>
        /// <item><description><para> * If CONSIDERCOMPANIONS is omitted, companions _will_ be considered.</para></description></item>
        /// <item><description><para> * This method _may_ return null if no suitable target could be located.</para></description></item>
        /// <item><description><para> * Any cached value is vacated and recalculated after a call to CreateBTPS_UpdateForBattlefieldConditions().</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-06:46UTC chinajade
        public static TorCharacter ForDispell(bool? considerCompanions = null, TorCharacterPredicateDelegate characterQualifier = null)
        {
            // If qualifiers provided, mark the cache as 'stale'...
            // We must assume the qualifier has changed since the last call.
            if (considerCompanions.HasValue || (characterQualifier != null))
                { Cache.TargetForDispell = null; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForDispell != null)
                { return (Cache.TargetForDispell); }

            considerCompanions = considerCompanions ?? true;
            characterQualifier = characterQualifier ?? (c => true);

            // Prefer to dispell party members with the maximum number of debuffs on them...
            // We weight this to prefer dispelling critical roles first.
            Cache.TargetForDispell = Me.PartyMembers(considerCompanions.Value)
                                        .OrderByDescending(c => c.Debuffs.Count() * c.PartyRoleWeight())
                                        .FirstOrDefault(c => characterQualifier(c) && !c.IsDead && c.Debuffs.Count() > 0);

            return (Cache.TargetForDispell);
        }

        /// <summary>
        /// <para>Selects an appropriate target on which to apply a Guard</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * Healers are preferred over all other roles.</para></description></item>
        /// <item><description><para> * Melee DPS are the second preference to be guarded.</para></description></item>
        /// <item><description><para> * After the above preferences, any other party member is considered for guard.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-07:21UTC chinajade
        public static TorCharacter ForGuard()
        {
            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForGuard != null)
                { return (Cache.TargetForGuard); }

            // Find a healer to guard...
            if ((Healer != null) && MinimalGuardQualifier(Healer))
            {
                Cache.TargetForGuard = Healer;
                return (Cache.TargetForGuard);
            }

            // Okay, anyone will do...
            // Weight preference to guard by party member role.
            Cache.TargetForGuard = Me.PartyMembers(true)
                                    .OrderByDescending(m => m.PartyRoleWeight())
                                    .FirstOrDefault(m => MinimalGuardQualifier(m));

            return (Cache.TargetForGuard);
        }
        private static TorCharacterPredicateDelegate MinimalGuardQualifier = (member => (member != Me) && !member.HasMirrorBuff("Guard"));

        /// <summary>
        /// <para>Returns the non-dead party member most in need of a heal.  If CONSIDERCOMPANIONS is true, companions are also
        /// consider in the healing evaluation.</para>
        /// <para>Party members (including companions) may be further qualified with CHARACTERQUALIFIER.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * Calling this method with 'no arguments' will prefer to use a previously calculated cached value, if one is available.</para>
        /// <para>If no cached value is available, an appropriate choice will be made, cached, and returned.</para></description></item>
        /// <item><description><para> * Calling this method with any argument will force the cache to be marked as 'stale',
        /// and re-evaluation occurs in accordance with the provided arguments.</para></description></item>
        /// <item><description><para> * If CONSIDERCOMPANIONS is omitted, companions _will_ be considered.</para></description></item>
        /// <item><description><para> * This method _may_ return null if no suitable target could be located.
        /// For instance, all party members are full health.</para></description></item>
        /// <item><description><para> * Any cached value is vacated and recalculated after a call to CreateBTPS_UpdateForBattlefieldConditions().</para></description></item>
        /// <item><description><para> * An "example use" is included as comments above the method definition.</para></description></item>
        /// </list></para>
        /// </summary>
        /// 17Jul2012-04:05UTC chinajade
        //
        // Example use:
        // // Casual heal maintenence (for anyone but healing Commando--can't use Hammer Shot on self)...
        // // ForHeal(with arguments) causes a new target to be calculated and cached.
        // new Decorator(ctx => TargetSelect.ForHeal(true, c => c != Me)) != null,
        //      // ForHeal("no arguments") uses the cached value
        //      Cast("Hammer Shot", hammerShotOn => TargetSelect.ForHeal(), hammerShotWhen => TargetSelect.ForHeal().Health <= Global.HighHealth))
        //
        // // Other healing (these abilities can be used on any target)...
        // // ForHeal(with arguments) causes a new target to be calculated and cached.
        // new Decorator(ctx => TargetSelect.ForHeal(true) != null, // ForHeal(with arguments) forces cache invalidation, and re-evaluation
        //      new PrioritySelector(
        //          // Determine "how much" healing to apply here...
        //          // ForHeal("no arguments") uses the cached value
        //          Cast("HealAbility1", healAbility1On => TargetSelect.ForHeal(), healAbility1When => TargetSelect.ForHeal().Health <= Global.MidHealth),
        //          Cast("HealAbility2", healAbility2On => TargetSelect.ForHeal(), healAbility2When => TargetSelect.ForHeal().Health <= Global.Critical),
        //          ...)),
        //    
        public static TorCharacter ForHeal(bool? considerCompanions = null, TorCharacterPredicateDelegate characterQualifier = null)                                   
        {
            // If qualifiers provided, mark the cache as 'stale'...
            // We must assume the qualifier has changed since the last call.
            if (considerCompanions.HasValue || (characterQualifier != null))
                { Cache.TargetForHeal = null; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForHeal != null)
                { return (Cache.TargetForHeal); }

            considerCompanions = considerCompanions ?? true;
            characterQualifier = characterQualifier ?? (c => true);

            // If our party role is not healing, limit our involvement...
            if (Me.IsInParty() && (Me != Healer))
                { characterQualifier = (c => characterQualifier(c) && (c.Health <= Global.Health.OffHealThreshold)); }

            // If tank in trouble, he gets priority...
            if ((Tank.Health < Global.Health.Mid) && characterQualifier(Tank))
            {
                Cache.TargetForHeal = Tank;
                return (Cache.TargetForHeal);
            }

            Cache.TargetForHeal = Me.PartyMembers(considerCompanions.Value)
                                    .OrderBy(c => c.HealthPercent)
                                    .FirstOrDefault(c => characterQualifier(c) && c.IsHealthInRange(Global.Health.NotDeadYet, Global.Health.Strong));

            return (Cache.TargetForHeal);
        }

        /// <summary>
        /// <para>Returns the 'best' target to interrupt.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * Calling this method with 'no arguments' will prefer to use a previously calculated cached value, if one is available.</para>
        /// <para>If no cached value is available, an appropriate choice will be made, cached, and returned.</para></description></item>
        /// <item><description><para> * Calling this method with any argument will force the cache to be marked as 'stale',
        /// and re-evaluation occurs in accordance with the provided arguments.</para></description></item>
        /// <item><description><para> * If CHARACTERQUALIIFER is null, returns the cached interrupt target if one has been calculated.</para></description></item>
        /// <item><description><para> * If CHARACTERQUALIFIER is null and there is no cached interrupt target, calculates the best interrupt target,
        /// caches it for future use, and returns it to the caller.</para></description></item>
        /// <item><description><para> * If CHARACTERQUALIFIER is not null, marks the cache entry as stale, and recalculates a new best interrupt target.</para></description></item>
        /// <item><description><para> * This method _may_ return null, if no suitable interrupt target was located.</para></description></item>
        /// <item><description><para> * Any cached value is vacated and recalculated after a call to CreateBTPS_UpdateForBattlefieldConditions().</para></description></item>
        /// <item><description><para> * An "example use" is included as comments above the method definition.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-06:46UTC chinajade
        ///
        // Example use:
        // // ForInterrupt(with arguments) forces evaluation of a new target, and caches it...
        // new Decorator(ctx => ForInterrupt(Global.MeleeRange) != null,
        //     new PrioritySelector(
        //         // ForInterrupt("no arguments") uses the cached value
        //         Cast("MeleeInterruptSpell1", MeleeInterruptSpell1On => TargetSelect.ForInterrupt())
        //         Cast("MeleeInterruptSpell2", MeleeInterruptSpell2On => TargetSelect.ForInterrupt())
        //         ))
        //
        // // ForInterrupt(with arguments) forces evaluation of a new target, and caches it...
        // new Decorator(ctx => ForInterrupt(Global.Distance.Ranged) != null,
        //      new PrioritySelector(
        //         // ForInterrupt("no arguments") uses the cached value
        //         Cast("RangeInterruptSpell3", RangeInterruptSpell3On => TargetSelect.ForInterrupt()),
        //         Cast("RangeInterruptSpell4", RangeInterruptSpell4On => TargetSelect.ForInterrupt())
        //         ))
        public static TorCharacter ForInterrupt(TorCharacterPredicateDelegate characterQualifier = null)
        {
            // If we're not managing the battlefield, honor BW's choice of current target...
            // We don't want to manage the battlefield in certain situations, such as when the user is controlling
            // movement.  The current target is the only one a user can 'see' (and thus position for attacking).
            if (!LazyRaider.IsBattlefieldManaged)
                { return BuddyTor.Me.CurrentTarget; }

            // If a qualifier was provided, mark the cache as 'stale'...
            // We must assume the qualifier has changed since the last call.
            if (characterQualifier != null)
                { Cache.TargetForInterrupt = null; }

            // If not in combat, nothing to do...
            if (!Me.InCombat)
            {
                Cache.TargetForInterrupt = null;
                return (Cache.TargetForInterrupt);
            }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForInterrupt != null)
                { return (Cache.TargetForInterrupt); }

            characterQualifier = characterQualifier ?? (c => true);

            // TODO: Would like to assess the spell being cast, and ignore mobs casting 'lesser' spells in this calculation.
            // But, BW currently gives us no way to examine the spell being cast by the mob.
            Cache.TargetForInterrupt = Me.EnemiesAttackers
                                        .OrderBy(t => t.RelativeDifficulty())
                                        .FirstOrDefault(t => characterQualifier(t) && t.IsCastTimeRemaining(TimeSpan.FromMilliseconds(750)));

            return (Cache.TargetForInterrupt);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="characterQualifier"></param>
        /// <returns></returns>
        /// 22Jul2012-23:18UTC chinajade
        public static TorCharacter ForShield(TorCharacterPredicateDelegate characterQualifier = null)
        {
            // If a qualifier was provided, mark the cache as 'stale'...
            // We must assume the qualifier has changed since the last call.
            if (characterQualifier != null)
                { Cache.TargetForShield = null; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForShield != null)
                { return (Cache.TargetForShield); }

            characterQualifier = characterQualifier ?? (c => true);     // resolve null characterQualifier to something sane

            // If our party role is not healing, limit our involvement...
            if (Me.IsInParty() && (Me.PartyRole() != Global.PartyRole.Healer))
                { characterQualifier = (c => characterQualifier(c) && (c.Health <= Global.Health.OffHealThreshold)); }

            // Shield tank, if needed...
            // Any shield on the tank abates damage, so tank is preferred.
            if ((Tank != null) && characterQualifier(Tank) && MinimalShieldQualifier(Tank))
            {
                Cache.TargetForShield = Tank;
                return (Cache.TargetForShield);
            }

            // Okay, any party member with non-trivial damage will do...
            Cache.TargetForShield = Me.PartyMembers(true)
                                    .OrderByDescending(m => m.PartyRoleWeight())
                                    .FirstOrDefault(member => (characterQualifier(member)
                                                                && member.Health <= Global.Health.High) && MinimalShieldQualifier(member));

            return (Cache.TargetForShield);
        }
        private static TorCharacterPredicateDelegate MinimalShieldQualifier = (m => !m.Debuffs.Any(d => Tunables.DebuffNames_Shielded.Contains(d.Name)));

        /// <summary>
        /// <para>Returns a mob we're fighting that is within RANGE and not suffering from DEBUFFNAME.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If omitted, the default RANGE is 3f (30 meters)</para></description></item>
        /// <item><description><para> * If all targets engaged have DEBUFFNAME, this method returns null.</para></description></item>
        /// <item><description><para> * This method is useful for debuffing all mobs we've currently engaged.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="debuffName"></param>
        /// <returns></returns>
        /// Neo93
        public static TorCharacter ForDebuff(string debuffName = null, float? range = null)
        {
            // If a non-null arguments provided, mark the cache as 'stale'...
            // We must assume the qualifiers have changed since the last call.
            if ((debuffName != null) || (range != null))
                { Cache.TargetForDebuff = null; }

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForDebuff != null)
                { return (Cache.TargetForDebuff); }

            Logger.ContractRequires(() => !string.IsNullOrEmpty(debuffName), () => "!string.IsNullOrEmpty(debuffName)");
            range = range ?? 3f;    // Resolve null range to something sane

            Cache.TargetForDebuff = Me.MobsAround(range.Value, c => c.InCombat && !c.HasDebuff(debuffName))
                                    .OrderByDescending(m => m.HealthPercent)
                                    .FirstOrDefault(m => !m.IsDead);

            return Cache.TargetForDebuff;
        }

        /// <summary>
        /// <para>Select a mob for taunting, if a suitable candidate can be located.</para>
        /// <para>A mob requiring Taunt is a temporary, high-priority target.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If a mob is on a healer, ForTaunt() requests all classes taunt or knock down this mob.
        /// Other than this situation, non-Tank classes will never have a taunt target.</para></description></item>
        /// <item><description><para> * Tank classes will be asked to taunt any significant (i.e., non-trash) mob that is attacking
        /// another party member.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        /// 17Jul2012-06:46UTC chinajade
        public static TorCharacter ForTaunt()
        {
            //*****
            // BW BUG: Asking about a mob's current target causes exceptions (like this one: http://pastie.org/private/ex0jvuqvgkmrcm2settq2a)
            // E.g., if (Me.CurrentTarget.CurrentTarget != Me) // throws exception
            // This makes it nigh impossible to write code that can figure out what to taunt.
            // We return nothing here until BW API is repaired on this.
            return (null);
            //*****

            // If cached value is still viable, use it...
            // NB: Recall that the cached value is vacated on each call to CreateBTPS_UpdateForBattlefieldConditions().
            // Thus, the cached value is re-calculated on each 'pulse' of the CC PrioritySelector.  As such, there
            // is no need to do a lot of "cache stale"-type checks here.
            if (Cache.TargetForTaunt != null)
                { return (Cache.TargetForTaunt); }

            // If not in combat, nothing to do...
            if (!Me.InCombat)
            {
                Cache.TargetForTaunt = null;
                return (Cache.TargetForTaunt);
            }

            // Find a harder-to-kill mob not attacking tank, for taunting...
            // If multiple party members being attacked, we want to taunt off the more critical members first.
            float myRelativeDifficulty = Me.RelativeDifficulty();
            Cache.TargetForTaunt = Me.EnemiesAttackers
                                    .OrderByDescending(m => (m.CurrentTarget != null) ? m.CurrentTarget.PartyRoleWeight() : 0.00f)
                                    .ThenBy(m2 => m2.RelativeDifficulty())
                                    .FirstOrDefault(m3 => !m3.IsDeleted && !m3.IsDead
                                                            && (m3.RelativeDifficulty() > myRelativeDifficulty)
                                                            && !m3.IsCrowdControlled()
                                                            && (m3.CurrentTarget != Tank));

            // If no target found, we're done...
            if (Cache.TargetForTaunt == null)
                { return Cache.TargetForTaunt; }

            // If something is attacking healer, get it off or knock it down (regardless of role)...
            if (Cache.TargetForTaunt.CurrentTarget == Healer)
                { return Cache.TargetForTaunt; }

            // If our role is tank, return what we found; otherwise send non-Tank to their target choices...
            return (Me.IsInParty() && Me.IsPartyRoleTank()) ? Cache.TargetForTaunt : null;
        }

        /// <summary>
        /// <para>Returns the group's Healer.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * The returned value may be null, if a group healer cannot be located.</para></description></item>
        /// <item><description><para> * If a companion is to perform the healing role, be certain it is using the appropriate mode (e.g., "Med Watch") so it will be detected as a healer.</para></description></item>
        /// </list></para>
        /// </summary>
        public static TorCharacter Healer
        {
            get {
                // If cached value is still viable, use it...
                // NB: The "Healer" cached value is only vacated while out of combat.  If the healer dies while we're
                // in combat, we must recalculate a new value for it.
                if ((Cache.Healer != null) && !Cache.Healer.IsDead)
                    return (Cache.Healer);

                Cache.Healer = FindPartyMemberPerformingRoles(new [] { Global.PartyRole.Healer});
                return (Cache.Healer);
            }
        }

        /// <summary>
        /// <para>Returns the group's Tank.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * This value will never be null.  If a proper tank cannot be found, the companion is preferred.
        /// If no companion available, self is used.</para></description></item>
        /// <item><description><para> * If a companion is to perform a proper tanking role, its is only detected as a tank when using the appropriate mode (e.g., "Guard Stance").</para></description></item>
        /// </list></para>
        /// </summary>
        /// Neo93
        public static TorCharacter Tank
        {
            get {
                // If cached value is still viable, use it...
                // NB: The "Tank" cached value is only vacated while out of combat.  If the tank dies while we're
                // in combat, we must recalculate a new value for it.
                if ((Cache.Tank != null) && !Cache.Tank.IsDead)
                    return (Cache.Tank);

                Cache.Tank = FindPartyMemberPerformingRoles(new [] { Global.PartyRole.MeleeTank, Global.PartyRole.RangedTank })
                                     ?? ((Me.IsCompanionInUse() && !Me.Companion.IsDead) ? (TorCharacter)Me.Companion : (TorCharacter)Me);
                return (Cache.Tank);
            }
        }

        /// <summary>
        /// <para>Returns the list of targets that we are, or will be engaging.</para>
        /// <para><list type="bullet">
        /// <item><description><para> * If we are in combat, use the list of enemies attacking the party.</para></description></item>
        /// <item><description><para> * If we are not in combat, use the list of mobs around the CurrentTarget if we engage.</para></description></item>
        /// <item><description><para> * If there is no CurrentTarget, an empty list will be returned.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TorCharacter>    ViableTargetList()
        {
            if (Me.InCombat)
                { return (Me.EnemiesAttackers.Where(c => ViableTargetQualifier(c))); }

            else if (Me.CurrentTarget != null)
            {
                // NB: We need a LoS check, because we don't want to consider mobs that may be on the other side
                // of a wall, or standing right outside a building.
                // The Where clause is an optimization.  If we include its lambda expression as part of the MobsAround qualifier,
                // we'll be ray-casting every mob in the area.  This is horrible for high-density areas, as it would take that
                // query a couple of seconds to complete.
                return Me.CurrentTarget.MobsAround(1.7f)
                        .Where(c => (c == Me.CurrentTarget) || BuddyTor.IsInLineOfSight(Me.CurrentTarget, c.Position));
            }

            return (EmptyTargetList);
        }
        private readonly static IEnumerable<TorCharacter>       EmptyTargetList = new List<TorCharacter>();
        private readonly static TorCharacterPredicateDelegate   ViableTargetQualifier = (c => !c.IsDeleted && !c.IsDead);

        #region Private/Utility
        private class Cache
        {
            public static TorCharacter  Healer                  = null;
            public static TorCharacter  Tank                    = null;
            public static TorCharacter  TargetForAttack         = null;
            public static TorCharacter  TargetForCrowdControl   = null;
            public static TorCharacter  TargetForDebuff         = null;
            public static TorCharacter  TargetForDispell        = null;
            public static TorCharacter  TargetForGuard          = null;
            public static TorCharacter  TargetForHeal           = null;
            public static TorCharacter  TargetForInterrupt      = null;
            public static TorCharacter  TargetForShield         = null;
            public static TorCharacter  TargetForTaunt          = null;

            public static void Vacate()
            {
                // Mark the cache values as stale, for re-evaluation next time something is requested...
                TargetForAttack       = null;
                TargetForCrowdControl = null;
                TargetForDebuff       = null;
                TargetForDispell      = null;
                TargetForHeal         = null;
                TargetForInterrupt    = null;
                TargetForShield       = null;
                TargetForTaunt        = null;

                if (!Me.InCombat)   // Once calculated, some values don't change while in combat
                {
                    Healer          = null;
                    TargetForGuard  = null;
                    Tank            = null;
                }
            }
        }

        /// <summary>
        /// <para>Returns the party member performing one of the requested ROLES.  MEMBERQUALIFIER may be specified to narrow the selection.
        /// Prefers Players to Companions, if both performing the same role.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If MEMBERQUALIFIER is omitted, all party members will be considered.</para></description></item>
        /// <item><description><para> * If both a player and a companion is performing the same role, preference is given to the player.</para></description></item>
        /// <item><description><para> * If multiple players are performing the same role, the higher-level player will be preferred.</para></description></item>
        /// <item><description><para> * If no party members match the given role and qualifications, null is returned.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <param name="roles"></param>
        /// <param name="memberQualifier"></param>
        /// <returns></returns>
        /// 20Jul2012-23:59UTC chinajade
        private static TorCharacter FindPartyMemberPerformingRoles(Global.PartyRole[] roles, TorCharacterPredicateDelegate memberQualifier = null)
        {
            memberQualifier = memberQualifier ?? (m => true);       // resolve null memberQualifier to something sane

            // Prefer a player performing the request role...
            TorPlayer playerInRole = Me.PartyPlayers().OrderByDescending(c => c.Level).FirstOrDefault(c => memberQualifier(c) && roles.Contains(c.PartyRole()));
            if (playerInRole != null)
                { return (playerInRole); }

            // No player found.  See if a companion is performing the given role...
            return (Me.PartyCompanions().OrderByDescending(c => c.Level).FirstOrDefault(c => memberQualifier(c) && roles.Contains(c.PartyRole())));    
        }

        /// <summary>
        /// <para>Returns the "easiest to kill" target.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If we are in combat, the target is selected from the list of enemies attacking the party.</para></description></item>
        /// <item><description><para> * If we are not in combat, the target is selected from the nearby mobs we expect to engage if the CurrentTarget is pulled.</para></description></item>
        /// <item><description><para> * Other things being equal, nearer targets will be preferred over distant targets of similar difficulty.</para></description></item>
        /// <item><description><para> * If there is no CurrentTarget, null will be returned.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        private static TorCharacter GetEasiestTarget(TorCharacterPredicateDelegate characterQualifier = null)
        {
            characterQualifier = characterQualifier ?? (c => true);

            return (ViableTargetList()
                    .OrderBy(t => TargetValue_CloserIsLowerWeight(t))
                    .FirstOrDefault(c => characterQualifier(c)));
        }

        /// <summary>
        /// <para>Returns the "most difficult to kill" target.</para>
        /// <para>Notes:<list type="bullet">
        /// <item><description><para> * If we are in combat, the target is selected from the list of enemies attacking the party.</para></description></item>
        /// <item><description><para> * If we are not in combat, the target is selected from the nearby mobs we expect to engage if the CurrentTarget is pulled.</para></description></item>
        /// <item><description><para> * Other things being equal, nearer targets will be preferred over distant targets of similar difficulty.</para></description></item>
        /// <item><description><para> * If there is no CurrentTarget, null will be returned.</para></description></item>
        /// </list></para>
        /// </summary>
        /// <returns></returns>
        private static TorCharacter GetMostDifficultTarget(TorCharacterPredicateDelegate characterQualifier = null)
        {
            characterQualifier = characterQualifier ?? (c => true);

            return (ViableTargetList()
                    .OrderByDescending(t => TargetValue_CloserIsHigherWeight(t))
                    .FirstOrDefault(c => characterQualifier(c)));
        }

        /// <summary>
        /// <para>Values targets based on the target's RelativeDifficulty and distance.  For similar difficulty targets, near targets will have higher rating.</para>
        /// <para>The returned value represents a weight on the closed interval [0.0f..1.0f].</para>
        /// </summary>
        /// <param name="torCharacter"></param>
        /// <returns></returns>
        private static float TargetValue_CloserIsHigherWeight(TorCharacter torCharacter)
        {
            // NB: Recall that Buddywing represents 1 meter as 0.1f.
            float distanceRating = (0.1f / (0.1f + torCharacter.Distance));

            // Target difficulty is 75% of our assessment, and its range contributes another 25%
            return ((torCharacter.RelativeDifficulty() * 0.75f) + (distanceRating * 0.25f));
        }

        /// <summary>
        /// <para>Values targets based on the target's RelativeDifficulty and distance.  For similar difficulty targets, near targets will have lower rating.</para>
        /// <para>The returned value represents a weight on the closed interval [0.0f..1.0f].</para>
        /// </summary>
        /// <param name="torCharacter"></param>
        /// <returns></returns>
        private static float TargetValue_CloserIsLowerWeight(TorCharacter torCharacter)
        {
            // This is a 'cheat' calculation that preserves the interval [0.0f..1.0f]...
            // It assumes that a mob cannot attack us from 35 meters away (3.5f by Buddywing measure), so scales
            // everything using that arbitrary value.
            float distanceRating = (torCharacter.Distance / 3.5f);

            // Target difficulty is 75% of our assessment, and its range contributes another 25%
            return ((torCharacter.RelativeDifficulty() * 0.75f) + (distanceRating * 0.25f));
        }
        #endregion
    }
}
